// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2022 Ivan Baidakou

#include "common.h"
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <rotor-light.hpp>
#include <string.h>
#include <util/delay.h>

#define LED PB5

namespace rl = rotor_light;

static void toggle_led() { PORTB ^= (1 << LED); }

namespace message {

struct BlinkCommand : rl::Message {
  static constexpr auto type_id = __LINE__;
  using rl::Message::Message;
  rl::MessageTypeId get_type_id() const override { return type_id; }
};

} // namespace message

struct Blinker : rl::Actor<2> {
  using Parent = Actor<2>;

  void initialize() override {
    subscribe(&Blinker::on_blink_command);
    Parent::initialize();
  }

  void advance_start() override {
    Parent::advance_start();
    blink();
  }

  void blink() {
    toggle_led();
    add_event(
        delay,
        [](void *data) {
          auto self = static_cast<Blinker *>(data);
          self->send<message::BlinkCommand>(id);
        },
        this);
  }

  void on_blink_command(message::BlinkCommand &msg) { blink(); }

  rl::Duration delay;
};

static void app_hw_init();

using Storage = rl::traits::MessageStorage<rl::message::ChangeState,
                                           rl::message::ChangeStateAck,
                                           message::BlinkCommand>;
using Queue = rl::Queue<Storage, 5>; /* upto 5 messages in 1 queue */
using Planner = rl::Planner<2>;      /* upto 2 time events */
using Supervisor =
    rl::Supervisor<rl::SupervisorBase::min_handlers_amount, Blinker>;

Queue queue;
Planner planner;
rl::Context context{&queue, &planner, &get_now};
Supervisor sup;

int main(int, char **) {
  app_hw_init();

  /* setup */
  sup.bind(context);
  auto blinker = sup.get_child<0>();
  blinker->delay = rl::Duration{250000};
  /* let it polls timer */
  sup.start(true);

  /* main cycle */
  sup.process();
  return 0;
}

static void app_hw_init() {
  // Initialize the application including WDT, PORTB.5 and TIMER0

  // We will now disable the watchdog.
  // Service the watchdog just to be sure to avoid pending timeout.
  wdt_reset();

  // Clear WDRF in MCUSR.
  MCUSR &= ~(1U << WDRF);

  // Write logical one to WDCE and WDE.
  // Keep the old prescaler setting to prevent unintentional time-out.
  WDTCSR |= (1U << WDCE) | (1U << WDE);

  // Turn off the WDT.
  WDTCSR = 0x00;

  // We will now initialize PORTB.5 to be used as an LED driver port.
  // Set PORTB.5 value to low.
  PORTB &= ~(1U << PORTB5);

  /* initializing PB5 which is connected to port 13 of uno as output*/
  DDRB |= (1 << LED);

  enable_timer();

  sei();
}
